
import { ApiPageResult, ApiResult } from '@/api/interface'
import axios, {
  AxiosError,
  type AxiosInstance,
  type AxiosRequestConfig,
  type AxiosResponse,
  type InternalAxiosRequestConfig
} from 'axios'
import qs from 'qs'
/**
 * 接口请求配置信息
 */
export interface RequestConfig extends InternalAxiosRequestConfig {
  loading?: boolean
  cancel?: boolean
}

const config = {
  // 默认地址请求地址，可在 .env.** 文件中修改
  baseURL: import.meta.env.VITE_API_URL as string,
  // 设置超时时间
  timeout: 30000 as number,
  // 跨域时候允许携带凭证
  withCredentials: true
}

// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pendingMap = new Map<string, AbortController>()

// 序列化参数
export const getPendingUrl = (config: InternalAxiosRequestConfig<any>) =>
  [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&')

export class AxiosCanceler {
  /**
   * @description: 添加请求
   * @param {Object} config
   * @return void
   */
  addPending(config: RequestConfig) {
    // 在请求开始前，对之前的请求做检查取消操作
    this.removePending(config)
    const url = getPendingUrl(config)
    const controller = new AbortController()
    config.signal = controller.signal
    pendingMap.set(url, controller)
  }

  /**
   * @description: 移除请求
   * @param {Object} config
   */
  removePending(config: RequestConfig) {
    const url = getPendingUrl(config)
    // 如果在 pending 中存在当前请求标识，需要取消当前请求
    const controller = pendingMap.get(url)
    if (controller && config.method == 'POST') {
      controller.abort()
      pendingMap.delete(url)
    }
  }

  /**
   * @description 完成请求之后清除请求标识
   * @param config
   */
  finishPending(config: InternalAxiosRequestConfig<any>) {
    const url = getPendingUrl(config)
    pendingMap.delete(url)
  }

  /**
   * @description: 清空所有pending
   */
  removeAllPending() {
    pendingMap.forEach((controller) => {
      controller && controller.abort()
    })
    pendingMap.clear()
  }
}

/**
 * @description: 校验网络请求状态码
 * @param {Number} status
 * @return void
 */
export const checkStatus = (status: number) => {
  switch (status) {
    case 400:
      console.log('请求失败！请您稍后重试')
      break
    case 401:
      console.log('登录失效！请您重新登录')
      break
    case 403:
      console.log('当前账号无权限访问！')
      break
    case 404:
      console.log('你所访问的资源不存在！')
      break
    case 405:
      console.log('请求方式错误！请您稍后重试')
      break
    case 408:
      console.log('请求超时！请您稍后重试')
      break
    case 500:
      console.log('服务异常！')
      break
    case 502:
      console.log('网关错误！')
      break
    case 503:
      console.log('服务不可用！')
      break
    case 504:
      console.log('网关超时！')
      break
    default:
      console.log('请求失败！')
  }
}

const axiosCanceler = new AxiosCanceler()

class RequestHttp {
  service: AxiosInstance
  public constructor(config: AxiosRequestConfig) {
    // instantiation
    this.service = axios.create(config)

    /**
     * @description 请求拦截器
     * 客户端发送请求 -> [请求拦截器] -> 服务器
     * token校验 : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
     */
    this.service.interceptors.request.use(
      (config: RequestConfig) => {
        // 重复请求不需要取消，在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
        config.cancel ?? (config.cancel = true)
        if (config.params) {
          config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
          if (config.method == 'POST' || config.method == 'post') {
            config.data = qs.stringify(config.params)
            delete config.params
          }
        }
        config.cancel && axiosCanceler.addPending(config)
        return config
      },
      (error: AxiosError) => {
        return Promise.reject(error)
      }
    )

    /**
     * @description 响应拦截器
     *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
     */
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        const { data, config } = response
        axiosCanceler.finishPending(config)
        if (response.headers['content-disposition']) {
          //下载文件
          return response
        }
        // 成功请求（在页面上除非特殊情况，否则不用处理失败逻辑）
        return data
      },
      async (error: AxiosError) => {
        const { response, config } = error
        axiosCanceler.finishPending(config as InternalAxiosRequestConfig)
        // 请求超时 && 网络错误单独判断，没有 response
        if (error.message.indexOf('timeout') !== -1) console.log('请求超时！请您稍后重试')
        if (error.message.indexOf('Network Error') !== -1) console.log('网络错误！请您稍后重试')
        // 根据服务器响应的错误状态码，做不同的处理
        if (response) checkStatus(response.status)
        // 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:可以跳转到断网页面
        return Promise.reject(error)
      }
    )
  }
  get<T extends ApiResult>(url: string, params?: object, _object = {}): Promise<T> {
    return this.service.get(url, { params, ..._object })
  }
  getList<T>(url: string, params?: object, _object = {}): Promise<ApiPageResult<T>> {
    return this.service.get(url, { params, ..._object })
  }
  post<T extends ApiResult>(url: string, params?: object | string, _object = {}): Promise<T> {
    return this.service.post(url, params, _object)
  }
  postForm<T extends ApiResult>(url: string, params?: object | string, _object = {}): Promise<T> {
    return this.service.postForm(url, {}, { params, ..._object })
  }
  postList<T>(url: string, params?: object | string, _object = {}): Promise<ApiPageResult<T>> {
    return this.service.post(url, params, _object)
  }
  postFormList<T>(url: string, params?: object | string, _object = {}): Promise<ApiPageResult<T>> {
    return this.service.postForm(url, {}, { params, ..._object })
  }
  postFile<T>(url: string, data?: any, _object={}) : Promise<AxiosResponse<T>> {
    return this.service.post(url, data, _object);
  }
  put<T extends ApiResult>(url: string, params?: object, _object = {}): Promise<T> {
    return this.service.put(url, params, _object)
  }
  putForm<T extends ApiResult>(url: string, params?: object, _object = {}): Promise<T> {
    return this.service.putForm(url, {}, { params, ..._object })
  }
  delete<T extends ApiResult>(url: string, params?: any, _object = {}): Promise<T> {
    return this.service.delete(url, { params, ..._object })
  }
}

export default new RequestHttp(config)
